﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Marmat.Forms
{
    /// <summary>
    /// 虚拟滚动条的扩展支持。
    /// </summary>
    public class VirtualScrollableControlExtension
    {
        /// <summary>
        /// 构建滚动条的消息处理实例。
        /// </summary>
        /// <param name="control"></param>
        public VirtualScrollableControlExtension(Control control)
        {
            this.Control = control;
        }



        /// <summary>
        /// 滚动边框的大小
        /// </summary>
        private Size scrollBorderSize = new Size(17, 17);

        private bool is_paint;
        /// <summary>
        /// 滚动条的大小。
        /// </summary>
        private Size scrollSize = new Size(10, 10);
        /// <summary>
        /// 垂直滚动条的矩形位置。
        /// </summary>
        private Rectangle vertical_scroll_rect;
        /// <summary>
        /// 水平滚动条的矩形位置。
        /// </summary>
        private Rectangle horizontal_scroll_rect;
        /// <summary>
        /// 垂直滚动条的焦点状态。
        /// </summary>
        private bool vertical_scroll_foucs = false;
        /// <summary>
        /// 水平滚动条的焦点状态。
        /// </summary>
        private bool horizontal_scroll_foucs = false;
        /// <summary>
        /// 垂直滚动条的矩形区域。
        /// </summary>
        private Rectangle vertical_scroll_Rectangle;
        /// <summary>
        /// 水平滚动条的矩形区域。
        /// </summary>
        private Rectangle horizontal_scroll_Rectangle;
        /// <summary>
        /// 是否正在拖拽垂直滚动条。
        /// </summary>
        private protected bool drag_vertical_scroll = false;
        /// <summary>
        /// 是否正在拖拽水平滚动条。
        /// </summary>
        private protected bool drag_horizontal_scroll = false;
        /// <summary>
        /// 准备拖拽时滚动条的值。
        /// </summary>
        private int drag_scroll_value;
        /// <summary>
        /// 准备拖拽时鼠标的坐标。
        /// </summary>
        private Point drag_scroll_location;



        /// <summary>
        /// 获取或设置一个值，该值指示垂直滚动条是否可见。
        /// </summary>
        public bool VScroll { get; set; } = true;

        /// <summary>
        /// 获取或设置一个值，该值指示水平滚动条是否可见。
        /// </summary>
        public bool HScroll { get; set; } = true;

        /// <summary>
        /// 获取与垂直滚动条关联的特征。
        /// </summary>
        public VirtualScrollProperties VerticalScroll { get; } = new VirtualScrollProperties();

        /// <summary>
        /// 获取与水平滚动条关联的特征。
        /// </summary>
        public VirtualScrollProperties HorizontalScroll { get; } = new VirtualScrollProperties();

        /// <summary>
        /// 需要处理的滚动条控件。
        /// </summary>
        internal Control Control { get; }

        public event ScrollEventHandler Scroll;

        /// <summary>
        /// 滚动条默认状态下的画刷
        /// </summary>
        public Brush DefaultBrush { get; set; } = new SolidBrush(Color.FromArgb(109, 128, 128, 128));

        /// <summary>
        /// 滚动条处于焦点下的画刷
        /// </summary>
        public Brush HoverBrush { get; set; } = new SolidBrush(Color.FromArgb(210, 128, 128, 128));

        /// <summary>
        /// 设置滚动区域的边框大小。
        /// </summary>
        /// <param name="width">边框的宽度。</param>
        public void SetScrollBorderSize(int width)
        {
            this.scrollBorderSize = new Size(width, width);
        }

        /// <summary>
        /// 设置滚动条的大小。
        /// </summary>
        /// <param name="width">滚动条的宽度/高度。</param>
        public void SetScrollSize(int width)
        {
            this.scrollSize = new Size(width, width);
        }

        /// <summary>
        /// 处理 Windows 消息之前执行,用于修改 Windows 消息内容。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="arguments"></param>
        /// <returns>如果返回true,则需要用户执行 WndProc 函数</returns>
        public bool BeforeWndProc(ref Message m, out object arguments)
        {
            bool result = true;
            arguments = default;
            if (this.VerticalScroll.Maximum > this.Control.Height || this.HorizontalScroll.Maximum > this.Control.Width)
            {
                Point location = this.Control.PointToClient(System.Windows.Forms.Control.MousePosition);
                switch (m.Msg)
                {
                    case Win32API.Messages.WM_MOUSEMOVE:
                        // 如果当前正在拖动滚动条,则更新滚动条位置,并拦截原有事件。
                        if (this.drag_vertical_scroll)
                        {
                            double proportion = ((double)this.Control.Height - 0) / this.VerticalScroll.Maximum;
                            int value = drag_scroll_value + (int)((location.Y - drag_scroll_location.Y) / proportion);
                            this.set_vertical_scroll_value(value);
                            result = false;
                        }
                        break;
                    case Win32API.Messages.WM_MOUSEWHEEL:
                        var delta = GET_WHEEL_DELTA_WPARAM(m.WParam);
                        arguments = this.VerticalScroll.Value - delta;
                        break;
                    case Win32API.Messages.WM_LBUTTONDOWN:
                        // 如果鼠标处于滚动条矩形时按下，则开启拖动模式。
                        if (this.vertical_scroll_rect.Contains(location))
                        {
                            Win32API.User32.SetCapture(m.HWnd);
                            drag_vertical_scroll = true;
                            drag_scroll_value = this.VerticalScroll.Value;
                            drag_scroll_location = location;
                            result = false;
                        }
                        // 如果鼠标处于滚动条区域，则根据鼠标点击的位置决定上移或下移一格。
                        else if (this.vertical_scroll_Rectangle.Contains(location))
                        {
                            if (location.Y > vertical_scroll_rect.Bottom)
                                this.set_vertical_scroll_value(this.VerticalScroll.Value + this.Control.Height);
                            else if (location.Y < vertical_scroll_rect.Top)
                                this.set_vertical_scroll_value(this.VerticalScroll.Value - this.Control.Height);
                            result = false;
                        }
                        break;
                    case Win32API.Messages.WM_RBUTTONDOWN:
                    case Win32API.Messages.WM_MBUTTONDOWN:
                        if (this.vertical_scroll_Rectangle.Contains(location))
                            result = false;
                        break;
                    case Win32API.Messages.WM_LBUTTONUP:
                        // 关闭拖动模式,并释放鼠标捕捉事件。
                        if (drag_vertical_scroll)
                        {
                            drag_vertical_scroll = false;
                            this.Invalidate();
                        }
                        break;
                    case Win32API.Messages.WM_PAINT:
                        this.is_paint = false;
                        break;
                }
            }

            return result;
        }

        internal static int GET_WHEEL_DELTA_WPARAM(IntPtr wParam)
        {
            return (short)HIWORD(wParam);
        }

        internal static ushort HIWORD(IntPtr dwValue)
        {
            return (ushort)((((long)dwValue) >> 0x10) & 0xffff);
        }

        /// <summary>
        /// 处理 Windows 消息之后执行。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="arguments"></param>
        public void AfterWndProc(Message m, object arguments)
        {
            if (this.VerticalScroll.Maximum > this.Control.Height || this.HorizontalScroll.Maximum > this.Control.Width)
            {
                Point location = this.Control.PointToClient(System.Windows.Forms.Control.MousePosition);
                switch (m.Msg)
                {
                    case Win32API.Messages.WM_MOUSEWHEEL:
                        if (arguments != null)
                        {
                            int value = (int)arguments;
                            this.set_vertical_scroll_value(value);
                        }
                        break;

                    case Win32API.Messages.WM_MOUSEMOVE:
                        if (this.vertical_scroll_foucs && !vertical_scroll_rect.Contains(location))
                        {
                            this.Invalidate();
                        }
                        // 如果滚动条之前的状态无焦点,但是鼠标已经移入,则刷新滚动条的UI。
                        else if (!this.vertical_scroll_foucs && vertical_scroll_rect.Contains(location))
                        {
                            this.Invalidate();
                        }
                        break;
                    case Win32API.Messages.WM_PAINT:
                        if (!this.is_paint)
                        {
                            this.Invalidate(m.HWnd);
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// 设置垂直滚动条的值。
        /// </summary>
        /// <param name="value"></param>
        internal protected void set_vertical_scroll_value(int value)
        {
            value = Math.Max(0, value);
            value = Math.Min(value, Math.Max(0, this.VerticalScroll.Maximum - this.Control.Height));
            int oldValue = this.VerticalScroll.Value;
            if (oldValue != value)
            {
                this.VerticalScroll.Value = value;
                ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.ThumbPosition, oldValue, value, ScrollOrientation.VerticalScroll);
                this.Scroll?.Invoke(this.Control, args);
            }
        }

        public void Invalidate(IntPtr hWnd)
        {
            IntPtr dc = Win32API.User32.GetWindowDC(hWnd);
            using (Graphics graphics = Graphics.FromHdc(dc))
                this.OnPaint(new PaintEventArgs(graphics, this.Control.ClientRectangle));
            Win32API.User32.ReleaseDC(hWnd.ToInt32(), dc.ToInt32());
        }

        public void Invalidate()
        {
            this.Control.Invalidate();
        }
        /// <summary>
        ///  引发 System.Windows.Forms.Control.Paint 事件。
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnPaint(PaintEventArgs e)
        {
            this.is_paint = true;
            if (this.VerticalScroll.Maximum > this.Control.Height || this.HorizontalScroll.Maximum > this.Control.Width)
            {
                using (Bitmap bitmap = new Bitmap(this.scrollBorderSize.Width, this.Control.Height))
                {
                    using (Graphics g = Graphics.FromImage(bitmap))
                    {
                        GraphicsPath path = new GraphicsPath();
                        double proportion = ((double)this.Control.Height - 0) / this.VerticalScroll.Maximum;
                        int height = (int)(this.Control.Height * proportion);
                        int top = 0 + (int)(this.VerticalScroll.Value * proportion);
                        int left = (bitmap.Width - scrollSize.Width) / 2;
                        path.AddPie(new Rectangle(left, top, scrollSize.Width, scrollSize.Height), 180, 180);
                        path.AddRectangle(new Rectangle(left, top + scrollSize.Height / 2, scrollSize.Width, height - scrollSize.Height));
                        path.AddPie(new Rectangle(left, top + height - scrollSize.Height, scrollSize.Width, scrollSize.Height), 0, 180);
                        path.AddRectangle(new Rectangle(this.Control.Width - 17, 0, 17, this.Control.Height));
                        path.FillMode = FillMode.Winding;
                        vertical_scroll_Rectangle = new Rectangle(this.Control.Width - this.scrollBorderSize.Width, 0, this.scrollBorderSize.Width, this.Control.Height);
                        vertical_scroll_rect = new Rectangle(this.Control.Width - bitmap.Width + left, top, scrollSize.Width, height);
                        Brush brush = DefaultBrush;
                        Point position = this.Control.PointToClient(System.Windows.Forms.Control.MousePosition);
                        if (vertical_scroll_rect.Contains(position) || drag_vertical_scroll)
                            brush = HoverBrush;
                        vertical_scroll_foucs = vertical_scroll_rect.Contains(position);
                        //if (!this.VScroll)
                        //{
                        //    this.OnPaintScrollBackgroundImage(new PaintEventArgs(g, new Rectangle(0, 0, bitmap.Width, bitmap.Height)));
                        //}
                        g.SmoothingMode = SmoothingMode.HighQuality;
                        g.FillPath(brush, path);
                    }
                    var cipRectangle = new Rectangle(this.Control.Width - bitmap.Width, 0, bitmap.Width, this.Control.Height);
                    e.Graphics.DrawImage(bitmap, cipRectangle);
                }
            }
        }
    }
}
